package tcpclient

import (
	"bytes"
	"encoding/binary"
	"errors"
	log "github.com/sirupsen/logrus"
	"io"
	"net"
	"time"
	"usbipproxy/common"
	"usbipproxy/domain"
)

type TcpConn struct {
	domain.MsgPipeRepo
	connId int64
	url    string
	//dispatchCli *domain.DispatchClient
	conn   net.Conn
	closed bool
}

func (c *TcpConn) Stop() error {
	if c.closed {
		return nil
	}
	c.closed = true
	c.MsgPipeRepo.SendOutMsg(nil)
	return nil
}

func (c *TcpConn) Start() error {

	defer func() {
		c.MsgPipeRepo.CloseIn()
		log.Println("connection read channel closed. ")
	}()
	//c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	conn, err := net.Dial("tcp", c.url)
	if err != nil {
		log.Errorf("connect url:%v err:%v", err, c.url)
		return err
	}
	if tcpConn, ok := conn.(*net.TCPConn); ok {
		//tcpConn.SetNoDelay(true)
		tcpConn.SetReadBuffer(1024 * 1024)
		tcpConn.SetWriteBuffer(1024 * 1024)
	}

	defer conn.Close()

	c.conn = conn
	go c.runWrite()
	c.runRead()
	return nil
}

// readBinaryData 读取带长度前缀的二进制数据
func (c *TcpConn) readBinaryData() (*common.PacketHeader, []byte, error) {
	conn := c.conn

	var header common.PacketHeader

	// 读取4字节的长度前缀
	lenBuf := make([]byte, common.HeaderSize)
	n, err := io.ReadFull(conn, lenBuf)
	if err != nil {
		log.Infof("TcpConn readBinaryData err:%v", err)
		return nil, nil, err
	}
	if n != common.HeaderSize {
		log.Debugf("TcpConn readBinaryData n:%v not equal %v", n, common.HeaderSize)
	}

	if err := binary.Read(bytes.NewReader(lenBuf), binary.BigEndian, &header); err != nil {
		log.Warnf("TcpConn header pause error: %v", err)
		return nil, nil, err
	}

	// 验证魔数
	if header.Magic != common.MagicValue {
		log.Warnf("TcpConn invalid magic: 0x%X (should be 0x%X)", header.Magic, common.MagicValue)
		return nil, nil, errors.New("invalid magic")
	}
	log.Debugf("TcpConn recv head: Cmd=%d, DataLen=%d", header.Command, header.DataLen)

	// 将长度前缀转换为uint32
	dataLen := header.DataLen

	if dataLen == 0 {
		return &header, nil, nil
	}

	// 限制最大数据长度（10MB）
	if dataLen > 10*1024*1024 {
		return nil, nil, errors.New("data too large")
	}

	// 读取实际数据
	data := make([]byte, dataLen)
	if _, err := io.ReadFull(conn, data); err != nil {
		return nil, nil, err
	}

	return &header, data, nil
}

func (c *TcpConn) runRead() {
	log.Infof("TcpConn runRead goroutine start")

	for {
		header, buf, err := c.readBinaryData()
		if err != nil {
			log.Errorf("TcpConn read binary data err: %v", err)
			break
		} else {
			c.handlePacket(header.Command, buf)
		}
	}

	c.MsgPipeRepo.SendOutMsg(nil)
}

func (c *TcpConn) runWrite() {
	log.Infof("TcpConn runWrite goroutine start")
	defer func() {
		c.conn.Close()
		log.Infof("connection write channel closed. ")
	}()

	for msg := range c.MsgPipeRepo.GetOutMsg() {
		if msg == nil {
			log.Infof("TcpConn runWrite msg is nil")
			break
		}

		data, err := common.Marshal(msg, common.TypePb)
		if err != nil {
			log.Warnf("TcpConn runWrite marshal msg failed. msg: %+v err:%v\n", msg, err)
			continue
		}

		//包装
		dataByte := common.PackData(common.CmdData, data)
		writeTimeout := 30 * time.Second
		deadline := time.Now().Add(writeTimeout)
		if err := c.conn.SetWriteDeadline(deadline); err != nil {
			log.Warnf("SetWriteDeadline fail: %v", err)
			break
		}
		if n, err := common.BlockingWrite(c.conn, dataByte); err != nil {
			log.Warnf("TcpConn runWrite write to client failed. msg: %v err:%v\n", msg, err)
			break
		} else {
			log.Debugf("TcpConn runWrite send length:%v data_len:%v", n, len(dataByte))
		}

	}
	//_ = c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "OK"))
}

// 处理接收到的包
func (c *TcpConn) handlePacket(command uint16, data []byte) {
	switch command {
	case common.CmdData:
		log.Debugf("TcpConn handlePacket recv len(data):%v", len(data))
		recvMsg, _, err := common.Unmarshal(data)
		if err != nil {
			log.Printf("TcpConn handlePacket parse msg failed. msg: %s", string(data))
			break
		}
		//log.Printf("recvMsg: %v cmd:%d\n", recvMsg.String(), recvMsg.MsgType)
		c.MsgPipeRepo.RecvInMsg(recvMsg)

	case common.CmdHeartbeat:
		log.Debugf("TcpConn handlePacket heartbeat")

	default:
		log.Debugf("TcpConn handlePacket unkonwn cmd: %d", command)
	}
}

func (c *TcpConn) GetConnId() int64 {
	if c == nil {
		return 0
	}
	return c.connId
}

func NewTcpConn(connId int64, url string, msgPipe domain.MsgPipeRepo) domain.TcpConnSessRepo {
	return &TcpConn{
		connId:      connId,
		url:         url,
		MsgPipeRepo: msgPipe,
	}
}
